﻿using System;
using Grpc.Core;
using Grpc.Net.ClientFactory;
using K9Nano.RemoteClient.LoadBalancing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace K9Nano.RemoteClient
{
    public static class RemoteServiceCollectionExtensions
    {
        public static IHttpClientBuilder AddRemoteClient<TClient>(this IServiceCollection services, string uri)
            where TClient : ClientBase
        {
            if (uri == null) throw new ArgumentNullException(nameof(uri));

            var builder = services.AddGrpcClient<TClient>(config =>
            {
                config.Address = new Uri(uri);
                config.Interceptors.Add(new ExceptionInterceptor());
            });

            return builder;
        }


        public static IHttpClientBuilder AddRemoteClient<TClient>(this IServiceCollection services, string name, string uri)
            where TClient : ClientBase
        {
            if (uri == null) throw new ArgumentNullException(nameof(uri));

            var builder = services.AddGrpcClient<TClient>(name, config =>
            {
                config.Address = new Uri(uri);
                config.Interceptors.Add(new ExceptionInterceptor());
            });

            return builder;
        }

        [Obsolete]
        public static void AddRemoteClient(this IServiceCollection services,
            string name,
            Action<RemoteClientOptions> configureOptions,
            params Type[] clientTypes)
        {
            if (configureOptions == null) throw new ArgumentNullException(nameof(configureOptions));

            var options = new RemoteClientOptions();

            configureOptions(options);

            services.Configure<GrpcClientFactoryOptions>(name, opt =>
            {
                opt.Address = new Uri(options.Uri);
                opt.Interceptors.Add(new ExceptionInterceptor());
            });

            foreach (var clientType in clientTypes)
            {
                services.AddGrpcClientCore(name, clientType);
            }

            if (options.MaxPool > 1)
            {
                // grpc-dotnet 暂时不支持Client LB
                // https://github.com/grpc/grpc-dotnet/issues/521
                services.Configure<RemoteClientOptions>(name, o =>
                {
                    o.Uri = options.Uri;
                    o.MaxPool = options.MaxPool;
                });
                services.Replace(ServiceDescriptor.Singleton<GrpcClientFactory, RoundRobinGrpcClientFactory>());
                services.AddSingleton<RoundRobinGrpcChannelFactory>();
            }
        }

        public static IHttpClientBuilder AddRemoteClient<TClient>(this IServiceCollection services, Action<RemoteClientOptions> configureOptions)
            where TClient : ClientBase
        {
            if (configureOptions == null) throw new ArgumentNullException(nameof(configureOptions));

            var options = new RemoteClientOptions();

            configureOptions(options);

            var builder = AddRemoteClient<TClient>(services, options.Uri);

            if (options.MaxPool > 1)
            {
                // grpc-dotnet 暂时不支持Client LB
                services.Configure<RemoteClientOptions>(typeof(TClient).Name, o =>
                {
                    o.Uri = options.Uri;
                    o.MaxPool = options.MaxPool;
                });
                services.Replace(ServiceDescriptor.Singleton<GrpcClientFactory, RoundRobinGrpcClientFactory>());
                services.AddSingleton<RoundRobinGrpcChannelFactory>();
            }

            return builder;
        }


        /// <summary>
        /// 调用 https://github.com/grpc/grpc-dotnet/blob/master/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs#L287
        /// </summary>
        /// <param name="services"></param>
        /// <param name="name"></param>
        /// <param name="clientType"></param>
        /// <returns></returns>
        private static IHttpClientBuilder AddGrpcClientCore(this IServiceCollection services, string name, Type clientType)
        {
            var core = typeof(GrpcClientServiceExtensions).GetMethod("AddGrpcClientCore",
                genericParameterCount: 1,
                bindingAttr: System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static,
                binder: null,
                types: new[] { typeof(IServiceCollection), typeof(string) },
                modifiers: null);

            var result = core.MakeGenericMethod(clientType)
                  .Invoke(null, new object[] { services, name });

            return (IHttpClientBuilder)result;
        }

    }
}